package com.hibase.core.excel.util;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import com.hibase.common.exception.HibaseAssert;
import com.hibase.core.excel.annotation.ExcelProperty;
import com.hibase.core.excel.exception.ExcelCommonException;
import com.hibase.core.excel.vo.dynamic.DynamicExcelFieldInfo;
import com.hibase.core.web.annotation.HibaseValid;
import javassist.*;
import javassist.bytecode.AnnotationsAttribute;
import javassist.bytecode.ClassFile;
import javassist.bytecode.ConstPool;
import javassist.bytecode.annotation.*;

import javax.validation.constraints.NotNull;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * Javassist 工具
 *
 * @author <a href="mailto:xiongliyi@hichain.com">Ian Xiong</a>
 * @since 1.0.0
 */
public class JavassistUtil {
    public static final String GET_PREFIX = "get";
    static final String SET_PREFIX = "set";
    private static ConcurrentHashMap<ClassLoader, ClassPool> CLASS_POOL_MAP = new ConcurrentHashMap<ClassLoader, ClassPool>();

    /**
     * 通过类加载器获取缓存池
     *
     * @param loader 类加载器
     * @return 返回ClassPool
     */
    public static ClassPool getClassPool(ClassLoader loader) {
        if (null == loader) {
            return ClassPool.getDefault();
        }

        ClassPool pool = CLASS_POOL_MAP.get(loader);
        if (null == pool) {
            pool = new ClassPool(true);
            pool.appendClassPath(new LoaderClassPath(loader));
            CLASS_POOL_MAP.put(loader, pool);
        }
        return pool;
    }

    /**
     * 创建新的Excel模板实列
     *
     * @param dynamicExcelFieldInfo 字段信息
     * @return 返回新创建Excel模板实列
     */
    public static Class newInstance(DynamicExcelFieldInfo dynamicExcelFieldInfo) throws CannotCompileException, ClassNotFoundException {
        ClassPool cp = getClassPool(JavassistUtil.class.getClass().getClassLoader());
        CtClass ctClass = cp.getOrNull(dynamicExcelFieldInfo.getName());
        if (ctClass != null) {
            return Class.forName(dynamicExcelFieldInfo.getName(), Boolean.FALSE, cp.getClassLoader());
        }
        CtClass newInstance = null;
        try {
            // crate new class
            newInstance = cp.makeClass(dynamicExcelFieldInfo.getName());
            // create properties
            fillFields(cp, newInstance, dynamicExcelFieldInfo.getFields());
        } catch (Exception e) {
            if (newInstance != null) {
                newInstance.detach();
            }
            throw new ExcelCommonException(e);
        }
        return newInstance.toClass();
    }

    /**
     * 字段和属性
     *
     * @param cp       缓存
     * @param instance Class类型
     * @param fields   字段/属性信息
     */
    public static void fillFields(ClassPool cp, CtClass instance, Map<String, DynamicExcelFieldInfo.FieldInfo> fields) {

        HibaseAssert.isTrue(MapUtil.isNotEmpty(fields), "Field Info cannot be empty");

        ClassFile classFile = instance.getClassFile();
        ConstPool constPool = classFile.getConstPool();

        //  添加HibaseValid数据校验注解
        addValidatorAnnotation(classFile, constPool);


        // fill fields
        try {
            // add default constructor
            addNoArgsConstructor(instance);

            for (String fieldName : fields.keySet()) {
                DynamicExcelFieldInfo.FieldInfo field = fields.get(fieldName);

                String cameFieldName = StrUtil.toCamelCase(fieldName);

                // add field
                CtClass fieldClass = cp.get(field.getDataType());
                CtField ctField = new CtField(fieldClass, cameFieldName, instance);
                ctField.setModifiers(Modifier.PRIVATE);

                // add annotation
                AnnotationsAttribute annotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
                addExcelPropertiesAnnotation(constPool, field, annotationsAttribute);
                addNotNullAnnotation(constPool, field, annotationsAttribute);
                ctField.getFieldInfo().addAttribute(annotationsAttribute);

                // add method
                String methodName = StrUtil.upperFirst(cameFieldName);
                instance.addMethod(CtNewMethod.setter(SET_PREFIX + methodName, ctField));
                instance.addMethod(CtNewMethod.getter(GET_PREFIX + methodName, ctField));

                instance.addField(ctField);
            }

        } catch (NotFoundException e) {
            throw new ExcelCommonException("cannot find class because of" + e.toString());
        } catch (CannotCompileException e) {
            throw new ExcelCommonException("cannot compile exception  because of" + e.toString());
        } catch (ClassNotFoundException e) {
            throw new ExcelCommonException("cannot find easyexcel Converter  because of " + e.toString());
        }

        // CtClass释放 ctClass.detach();
        // 缓存落盘 CtClass.debugDump="/home/admin/code_cache/dump"; pool.appendClassPath(new DirClassPath("/home/admin/code_cache/dump"));
    }

    /**
     * 添加空参构造
     *
     * @param instance
     * @throws CannotCompileException
     */
    private static void addNoArgsConstructor(CtClass instance) throws CannotCompileException {
        CtConstructor ctConstructor = new CtConstructor(new CtClass[]{}, instance);
        ctConstructor.setBody("{}");
        instance.addConstructor(ctConstructor);
    }

    /**
     * 添加HibaseValid数据校验注解
     *
     * @param classFile
     * @param constPool
     */
    private static void addValidatorAnnotation(ClassFile classFile, ConstPool constPool) {

        AnnotationsAttribute validAnnotationsAttribute = new AnnotationsAttribute(constPool, AnnotationsAttribute.visibleTag);
        Annotation validAnnotation = new Annotation(HibaseValid.class.getName(), constPool);
        validAnnotationsAttribute.addAnnotation(validAnnotation);
        classFile.addAttribute(validAnnotationsAttribute);
    }

    /**
     * 添加ExcelProperties属性
     *
     * @param constPool            缓存池
     * @param field                字段/属性
     * @param annotationsAttribute 注解属性
     * @throws ClassNotFoundException
     */
    private static void addExcelPropertiesAnnotation(ConstPool constPool, DynamicExcelFieldInfo.FieldInfo field, AnnotationsAttribute annotationsAttribute) throws ClassNotFoundException {
        Annotation excelAnnotation = new Annotation(ExcelProperty.class.getName(), constPool);

        if (ArrayUtil.isNotEmpty(field.getHeadNames())) {

            StringMemberValue[] valueMembers = new StringMemberValue[field.getHeadNames().length];

            for (int i = 0; i < field.getHeadNames().length; i++) {
                valueMembers[i] = new StringMemberValue(field.getHeadNames()[i], constPool);
            }

            ArrayMemberValue memberValue = new ArrayMemberValue(constPool);
            memberValue.setValue(valueMembers);

            excelAnnotation.addMemberValue("value", memberValue);
        }


        if (field.getIndex() != null) {
            excelAnnotation.addMemberValue("index", new IntegerMemberValue(constPool, field.getIndex()));
        }

        if (field.getConverter() != null) {
            Class<?> converter = Class.forName(field.getConverter());
            excelAnnotation.addMemberValue("converter", new ClassMemberValue(converter.getClass().getName(), constPool));
        }

        annotationsAttribute.addAnnotation(excelAnnotation);
    }


    /**
     * 添加非空注解
     *
     * @param constPool            缓存池
     * @param field                字段/属性
     * @param annotationsAttribute 注解属性
     */
    private static void addNotNullAnnotation(ConstPool constPool, DynamicExcelFieldInfo.FieldInfo field, AnnotationsAttribute annotationsAttribute) {
        if (field.getNotNull()) {
            Annotation notNullAnnotation = new Annotation(NotNull.class.getName(), constPool);
            String filedName = field.getKey();
            if (ArrayUtil.isNotEmpty(field.getHeadNames())) {
                filedName = ArrayUtil.join(field.getHeadNames(), "_");
            }
            notNullAnnotation.addMemberValue("message", new StringMemberValue(filedName.concat("必填！"), constPool));
            annotationsAttribute.addAnnotation(notNullAnnotation);
        }
    }
}

